﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Castle.DynamicProxy;
using System.Reflection;
using Castle.Core.Interceptor;

namespace EasyProp
{
    public class EasyPropBuilder
    {
        public T Build<T>(params object[] args) where T : class
        {
            return (T)Build(typeof(T), args);
        }

        public object Build(Type typeToBuild, params object[] args)
        {
            PropertySetInterceptor propertySetInterceptor = CreateInterceptor(typeToBuild);
            return _proxyGenerator.CreateClassProxy(typeToBuild, new IInterceptor[]{ propertySetInterceptor}, args);
        }

        private static PropertySetInterceptor CreateInterceptor(Type typeToIntercept)
        {
            PropertySetInterceptor interceptor = new PropertySetInterceptor();

            List<IPropertySetterFilter> beforeFilterChain = CreateFilterChain<BeforePropertySetFilterAttribute>(typeToIntercept);
            List<IPropertySetterFilter> afterFilterChain = CreateFilterChain<AfterPropertySetFilterAttribute>(typeToIntercept);

            beforeFilterChain.ForEach(beforeFilter => interceptor.AddBeforeFilter(beforeFilter));
            afterFilterChain.ForEach(afterFilter => interceptor.AddAfterFilter(afterFilter));
           
            return interceptor;
        }

        // Internal for unit testing.
        internal static List<IPropertySetterFilter> CreateFilterChain<TAttribute>(Type type)
            where TAttribute : PropertySetFilterAttributeBase
        {
            List<IPropertySetterFilter> chain = new List<IPropertySetterFilter>();
            foreach (PropertySetFilterAttributeBase attribute in GetEasyPropClassAttributesChain<TAttribute>(type))
            {
                foreach (Type commandType in attribute.FilterTypes)
                {
                    chain.Add(Activator.CreateInstance(commandType) as IPropertySetterFilter);
                }
            }
            return chain;
        }

        private static List<PropertySetFilterAttributeBase> GetEasyPropClassAttributesChain<TAttribute>(Type typeToIntercept)
            where TAttribute : PropertySetFilterAttributeBase
        {
            if (!theFilterAttributesChainCache.ContainsKey(typeof(TAttribute)))
            {
                theFilterAttributesChainCache[typeof(TAttribute)] = new AttributeChainOnType();
            }

            if (!theFilterAttributesChainCache[typeof(TAttribute)].ContainsKey(typeToIntercept))
            {
                theFilterAttributesChainCache[typeof(TAttribute)].Add(typeToIntercept, BuildAttributeChain<TAttribute>(typeToIntercept));
            }

            return theFilterAttributesChainCache[typeof(TAttribute)][typeToIntercept];
        }

        private static List<PropertySetFilterAttributeBase> BuildAttributeChain<TAttribute>(Type type) where TAttribute : PropertySetFilterAttributeBase
        {
            List<PropertySetFilterAttributeBase> customAttributes = new List<PropertySetFilterAttributeBase>();
            Type typeToSearch = type;
            while (typeToSearch != null)
            {
                IEnumerable<PropertySetFilterAttributeBase> attributesOnTypeToSearch =
                    typeToSearch.GetCustomAttributes(typeof(TAttribute), false).Cast<PropertySetFilterAttributeBase>();

                customAttributes.AddRange(attributesOnTypeToSearch);
                typeToSearch = typeToSearch.BaseType;
            }

            // Filter chaining is from top->down in inheritance hierarchy, we must reverse the list.
            customAttributes.Reverse();
            return customAttributes;
        }

        ProxyGenerator _proxyGenerator = new ProxyGenerator();

        static Dictionary<Type, AttributeChainOnType> theFilterAttributesChainCache = new Dictionary<Type, AttributeChainOnType>();

        private class AttributeChainOnType : Dictionary<Type, List<PropertySetFilterAttributeBase>> { }

    }
}
